package com.client.koshercook.server;


import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import javax.jdo.annotations.Embedded;
import javax.jdo.annotations.EmbeddedOnly;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.gwt.user.client.rpc.IsSerializable;


@PersistenceCapable
public class Ingredient implements IsSerializable, Comparable<Ingredient>{
	    @PrimaryKey
	    @Persistent//(valueStrategy = IdGeneratorStrategy.IDENTITY)
	    private Key key; //the recipe key in data store
	    
	    @Persistent
	    private String name; //the name of the Ingredient

	    //@Persistent
	    //private String site; //a site with explanation about the ingredient (future)
	    
	    @Persistent
	    private String picture; // link to a picture of the ingredient

	    @Persistent
	    private short kosher; //say what type is the ingredient
	    
	    @Persistent
	    private String[] alternativeNames; //list of alternative names for the ingredient
	           
	    @Persistent
	    private List<RecipeInfo> inRecipes; //a list of recipes which include this ingredient
	    @Persistent
	    private List<Integer> inRecipesPositions; //a list of recipes positions for sorting
	    
	    @Persistent
	    private int numOfRecipes=0; //number of recipes which include this ingredient. faster than get size every time
	    
	    /** constructor used to create new ingredient*/
	    public Ingredient(String name, String picture, short kosher,String... alternativeNames) {
	    	this.name = name;
	    	this.key = KeyFactory.createKey(Ingredient.class.getSimpleName(), name);
	    	//this.site = site;
	        this.picture = picture;
	        this.alternativeNames = alternativeNames;
	        this.kosher = kosher;
	        this.inRecipes = new ArrayList<RecipeInfo>();
	        this.inRecipesPositions = new ArrayList<Integer>();
	    }
	    
	    /**constructor to create a copy of an ingredient*/
		public Ingredient(Ingredient ing){
			this.name = ing.getIngredientName();
			this.kosher = ing.getKosher();
			this.key = KeyFactory.createKey(Ingredient.class.getSimpleName(), name);
			this.picture = ing.getPicture();
			this.inRecipes = new ArrayList<RecipeInfo>();
			for(RecipeInfo ri : ing.getInRecipes())
				this.inRecipes.add(new RecipeInfo(ri));
			this.inRecipesPositions = new ArrayList<Integer>();
			this.inRecipesPositions.addAll(ing.getInRecipesPositions());
			this.numOfRecipes = inRecipes.size();
			//I guess alter names is not needed in the copy
		}
		
		/**constructor to create a copy of an ingredient*/
		public Ingredient(Ingredient ing,int numOfRecipeInfos){
			this.name = ing.getIngredientName();
			this.kosher = ing.getKosher();
			this.key = KeyFactory.createKey(Ingredient.class.getSimpleName(), name);
			this.picture = ing.getPicture();
			this.inRecipesPositions = new ArrayList<Integer>();
			this.inRecipesPositions.addAll(ing.getInRecipesPositions());
			this.numOfRecipes = ing.getInRecipes().size();
			
			this.inRecipes = new ArrayList<RecipeInfo>();
			if(numOfRecipeInfos>=ing.getInRecipes().size()) {
//				numOfRecipeInfos = ing.getInRecipes().size();
				for(RecipeInfo ri : ing.getInRecipes())
					this.inRecipes.add(new RecipeInfo(ri));
			}
			else {
				for(int i=0 ; i<numOfRecipeInfos ; i++ ) {
					System.out.println("ri include recipe "+ing.getInRecipesPositions().get(i)+" in pos "+i);
					this.inRecipes.add(new RecipeInfo(ing.getInRecipes().get(ing.getInRecipesPositions().get(i))));
				}
			}
				
		}
		
		/**constructor to create a copy of an ingredient, with recipeInfos that includes otherMustIngs (for AND)*/
		public Ingredient(Ingredient ing,int numOfRecipeInfos,int maxResults,List<Key> otherMustIngs,short kosher,String type,List<String> forbiddenSites,int cookTimeLimit){
			this.name = ing.getIngredientName();
			this.kosher = ing.getKosher();
			this.key = KeyFactory.createKey(Ingredient.class.getSimpleName(), name);
			this.picture = ing.getPicture();
			this.inRecipesPositions = new ArrayList<Integer>();
			this.inRecipesPositions.addAll(ing.getInRecipesPositions());
			this.numOfRecipes = ing.getInRecipes().size();
			
			this.inRecipes = new ArrayList<RecipeInfo>();
			if(numOfRecipeInfos>=ing.getInRecipes().size()) {
				numOfRecipeInfos = ing.getInRecipes().size();
			}
			RecipeInfo ri;
			int results=0;
			for(int i=0 ; i<numOfRecipeInfos ; i++ ) {
				if(maxResults<results) break;
				System.out.println("ri include recipe "+ing.getInRecipesPositions().get(i)+" in pos "+i);
				ri = ing.getInRecipes().get(ing.getInRecipesPositions().get(i));
				if(ri.getIngredients().containsAll(otherMustIngs) && ri.suitFilters(kosher, type,forbiddenSites,cookTimeLimit)) {
					this.inRecipes.add(new RecipeInfo(ri));
					results++;
				}
			}
				
		}
		
		
		/**constructor to create a copy of an ingredient, without recipeInfos in links (for OR)*/
		public Ingredient(Ingredient ing,int numOfRecipeInfos,Set<String> links,short kosher,String type,List<String> forbiddenSites,int cookTimeLimit){
			this.name = ing.getIngredientName();
			this.kosher = ing.getKosher();
			this.key = KeyFactory.createKey(Ingredient.class.getSimpleName(), name);
			this.picture = ing.getPicture();
			this.inRecipesPositions = new ArrayList<Integer>();
			this.inRecipesPositions.addAll(ing.getInRecipesPositions());
			this.numOfRecipes = ing.getInRecipes().size();
			
			this.inRecipes = new ArrayList<RecipeInfo>();
			if(numOfRecipeInfos>=ing.getInRecipes().size()) {
				numOfRecipeInfos = ing.getInRecipes().size();
			}
			RecipeInfo ri;
			for(int i=0 ; i<numOfRecipeInfos ; i++ ) {
				System.out.println("ri include recipe "+ing.getInRecipesPositions().get(i)+" in pos "+i);
				ri = ing.getInRecipes().get(ing.getInRecipesPositions().get(i));
				if(!links.contains(ri.getLink()) && ri.suitFilters(kosher, type, forbiddenSites,cookTimeLimit))
					this.inRecipes.add(new RecipeInfo(ri));
			}
				
		}

	    public Key getKey() {
	    	return key;
	    }

	    /*public String getSite() {
	        return site;
	    }*/ 
	    
	    public String getIngredientName() {
	        return name;
	    }
	    
	    public String getPicture() {
	        return picture;
	    }

	    public String[] getalternativeNames() {
	        return alternativeNames;
	    }
	    
	    public short getKosher() {
	    	return kosher;
	    }
	    
	    public void setIngredientName(String ingredientName) {
	        this.name = ingredientName;
	    }
	    
	    /* public void setSite(String site){
	    	this.site=site;
	    } */
	    
	    public void addAlternativeName(String name){
	    	String[] newList = new String[this.alternativeNames.length+1];
            
	    	System.arraycopy(alternativeNames, 0, newList, 0,alternativeNames.length);
	    	newList[alternativeNames.length] = name;
	    	this.alternativeNames = newList;
	    }
	    
	    public void setPicture(String picture){
	    	this.picture=picture;
	    }
	    
	    public void changeKosher(short kosher){
	    	this.kosher = kosher;
	    }

	   	/** checks if ingredient is alternative name for this ingredient*/
		public boolean isAlternativeNameFor(String ingredient) {
			for(String subList : alternativeNames)
				if(ingredient.equalsIgnoreCase(subList))
					return true;
			return false;
		}
		
		/**checks if ingredient is substring of the alternative names for this ingredient*/
		public boolean isPartOfAlternativeName (String subString)
		{
			for(String subList : alternativeNames)
				if(subList.contains(subString))
					return true;
			return false;
		}
		
		/** checks if ingredient has an alternative name(any alternative) */
		public boolean hasAlternativeName() {
			if (alternativeNames.length > 0)
				return true;
			else
				return false;
		}
		
		/** checks if ingredient has name, kosher */
		public boolean isValid() {
			return name!=null && !name.isEmpty() && kosher!=0;
		}

		/** add a recipe to inRecipes set*/
		public void addInRecipes(RecipeInfo recipe) {
			inRecipes.add(recipe);
			updatePositionOf(numOfRecipes);
			numOfRecipes++;	
		}
		
		/** delete a recipe from inRecipes set
		 * @return the RecipeInfo which was removed*/
		public RecipeInfo deleteInRecipes(String rec_link) {
			RecipeInfo del=null;
			for(int i=0 ; i< inRecipes.size() ; i++) {
				if(inRecipes.get(i).getLink().equals(rec_link)) {
					del = inRecipes.remove(i);
					deletePositionOf(i);
					numOfRecipes--;
					break;
				}
			}
			return del;
		}
		
		/* remove a recipe from inRecipes set
		 * @pre No duplicated recipeInfos in the inRecipes list*
		public void deleteInRecipes(Key recipe) {
			int i=0;
			for(RecipeInfo ri : inRecipes) {
				if(ri.getKey().equals(recipe)) {
					inRecipes.remove(i);
					deletePositionOf(i);
					break;
				}
				i++;	
			}
			numOfRecipes--;
		}
*/		
		/* replace the inRecipes set/
		public void setInRecipes(List<RecipeInfo> inRecipes) {
			this.inRecipes=inRecipes;
			numOfRecipes=inRecipes.size();
		}*/

		/**get inRecipes set*/
		public List<RecipeInfo> getInRecipes() {
			return inRecipes;
		}
		
		/**get inRecipesPositions set*/
		public List<Integer> getInRecipesPositions() {
			return inRecipesPositions;
		}
		
		/**get inRecipes.size()*/
		public int getNumOfRecipes() {
			return numOfRecipes;
		}
		
		public void setNumOfRecipes(int numOfRecipes) {
			this.numOfRecipes =numOfRecipes;			
		}
		
		
		/** update inRecipesPositions and inRecipesNumPositions */
		public void updatePositions() {
			int i;
			if(this.inRecipesPositions==null) this.inRecipesPositions = new ArrayList<Integer>();
			numOfRecipes = this.inRecipes.size();
			for(i=0; i<numOfRecipes; i++) { //the rec we want to put in the right place
				if(!this.inRecipesPositions.contains(i))
					updatePositionOf(i);
			}
		}
		
		/**
		 * update the position of an element
		 * @param i - the position in InRecipes
		 */
		private void updatePositionOf(int i) {
			int num,j;
			num=inRecipes.get(i).getIngredients().size();			
			for(j=0 ; j<=inRecipesPositions.size() ; j++ ) {
				if(j==inRecipesPositions.size()) {//if finished all the list, append to the end
					this.inRecipesPositions.add(i); 
					break;
				}
				else if(num<inRecipes.get(inRecipesPositions.get(j)).getIngredients().size()) {
					this.inRecipesPositions.add(j,i); //put i in position j
					break;
				}
			}
		}
		
		/**
		 * delete the element i from inRecipesPositions
		 */
		private void deletePositionOf(int i) {
			this.inRecipesPositions.remove((Integer)i);
		}
		
		
		/**get first num of inRecipes (or less if there aren't enough) starting at 'from', according to their number of ings
		 * if 'from' >  size of list, return Null*/
		   
		public List<RecipeInfo> getFirstInRecipes(int from,int num) {
			
			System.out.println("INGREDIENT: got " + from + "    "  + num);
			System.out.println("INGREDIENT: inrecipes size = " + inRecipesPositions.size());
			
			
			if(this.inRecipes.size()<this.numOfRecipes) {
				num = Math.min(num,this.inRecipes.size()-from);
				return this.inRecipes.subList(from,num);
			}
				
				
			//TODO replace size of inRecipesPositions with numOfRecipes
			List<RecipeInfo> res = new ArrayList<RecipeInfo>();
			int bring = 0;
			if (num <= 0)
				return res;
			if (from >= inRecipesPositions.size()){          //not enough elements
				return res;
			}
			else{
				if (from + num >  inRecipesPositions.size())
					bring =  inRecipesPositions.size() - from;
				else
					bring = num;
			}
			
			System.out.println("INGREDIENT: stat loop, bring = " + bring + "   inrecSize = " + inRecipesPositions.size());
			
	//		int bring = Math.min(num,this.numOfRecipes-from);
			for(int i=from; i<from+bring ; i++) {
				res.add(this.inRecipes.get(this.inRecipesPositions.get(i)));
			}
			return res;
		}
		
		/** re-create inRecipesPositions and inRecipesNumPositions */
		public void createPositions() {
			if(this.inRecipesPositions==null) this.inRecipesPositions = new ArrayList<Integer>();
			else this.inRecipesPositions.clear();
			updatePositions();
		}
		
		
		/**compare function for sorting in decreasing way according to numOfRecipes
		 * @example Collections.sort(list{4,7,2}) gives list{7,4,2}
		 */
		public int compareTo(Ingredient other) {
			if(numOfRecipes<other.getNumOfRecipes()) return -1;
			if(numOfRecipes>other.getNumOfRecipes()) return 1;
			return key.compareTo(other.key);
		}

}